package com.usbserial.transaction;

import com.usbserial.core.DataListener;
import com.usbserial.core.SerialPort;
import com.usbserial.exception.UsbSerialException;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * High-level helper that mirrors the Dart Transaction API for synchronous request/response patterns.
 */
public class Transaction<T> implements AutoCloseable {

    private final SerialPort port;
    private final FrameDecoder<T> decoder;
    private final LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();
    private final DataListener listener = new DataListener() {
        @Override
        public void onDataReceived(byte[] data) {
            List<T> frames = decoder.decode(data);
            for (T frame : frames) {
                queue.offer(frame);
            }
        }

        @Override
        public void onError(Exception error) {
            // Surface errors by clearing state and adding null sentinel if desired.
        }
    };

    public Transaction(SerialPort port, FrameDecoder<T> decoder) {
        this.port = port;
        this.decoder = decoder;
        this.port.addDataListener(listener);
    }

    public static Transaction<byte[]> terminated(SerialPort port, byte[] terminator, int maxLength, boolean stripTerminator) {
        return new Transaction<>(port, new TerminatedFrameDecoder(terminator, maxLength, stripTerminator));
    }

    public static Transaction<String> stringTerminated(SerialPort port, byte[] terminator, int maxLength, boolean stripTerminator) {
        return new Transaction<>(port, new TerminatedStringDecoder(terminator, maxLength, stripTerminator, java.nio.charset.StandardCharsets.UTF_8));
    }

    public static Transaction<byte[]> magicHeader(SerialPort port, int[] headerPattern, int maxLength, long clearTimeoutMillis) {
        return new Transaction<>(port, new MagicHeaderLengthDecoder(headerPattern, maxLength, clearTimeoutMillis));
    }

    public static Transaction<byte[]> magicHeader(SerialPort port, int[] headerPattern) {
        return new Transaction<>(port, new MagicHeaderLengthDecoder(headerPattern));
    }

    public void flush() {
        queue.clear();
        decoder.reset();
    }

    public T getNext(long timeoutMillis) throws InterruptedException {
        return queue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
    }

    public T transact(byte[] payload, long timeoutMillis) throws UsbSerialException, InterruptedException {
        flush();
        port.write(payload);
        return getNext(timeoutMillis);
    }

    @Override
    public void close() throws IOException {
        port.removeDataListener(listener);
        decoder.close();
    }
}
